Skip to content

Conversation

@a-tarasyuk
Copy link
Member

Fixes #163498


This PR addresses the issue of confusing diagnostics for lambdas with init-captures appearing inside braced initializers.

Cases such as:

S s{[a(42), &] {}};

were misparsed as C99 array designators, producing unrelated diagnostics, such as use of undeclared identifier 'a', and expected ']'


if (MayBeDesignationStart())

if (ParseLambdaIntroducer(Intro, &ParseResult)) {

/// \param Tentative If non-null, we are disambiguating between a
/// lambda-introducer and some other construct. In this mode, we do not
/// produce any diagnostics or take any other irreversible action
/// unless we're sure that this is a lambda-expression.

} else if (Tok.isOneOf(tok::amp, tok::equal) &&
NextToken().isOneOf(tok::comma, tok::r_square) &&
Intro.Default == LCD_None) {
// We have a lone "&" or "=" which is either a misplaced capture-default
// or the start of a capture (in the "&" case) with the rest of the
// capture missing. Both are an error but a misplaced capture-default
// is more likely if we don't already have a capture default.
return Invalid(
[&] { Diag(Tok.getLocation(), diag::err_capture_default_first); });

The tentative parser now returns Incomplete for partially valid lambda introducers, preserving the lambda interpretation and allowing the proper diagnostic to be issued later.


Clang now correctly recognizes such constructs as malformed lambda introducers and emits the expected diagnostic — for example, “capture-default must be first” — consistent with direct initialization cases such as:

S s([a(42), &] {});

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Nov 3, 2025
@llvmbot
Copy link
Member

llvmbot commented Nov 3, 2025

@llvm/pr-subscribers-clang

Author: Oleksandr T. (a-tarasyuk)

Changes

Fixes #163498


This PR addresses the issue of confusing diagnostics for lambdas with init-captures appearing inside braced initializers.

Cases such as:

S s{[a(42), &] {}};

were misparsed as C99 array designators, producing unrelated diagnostics, such as use of undeclared identifier 'a', and expected ']'


if (MayBeDesignationStart())

if (ParseLambdaIntroducer(Intro, &ParseResult)) {

/// \param Tentative If non-null, we are disambiguating between a
/// lambda-introducer and some other construct. In this mode, we do not
/// produce any diagnostics or take any other irreversible action
/// unless we're sure that this is a lambda-expression.

} else if (Tok.isOneOf(tok::amp, tok::equal) &&
NextToken().isOneOf(tok::comma, tok::r_square) &&
Intro.Default == LCD_None) {
// We have a lone "&" or "=" which is either a misplaced capture-default
// or the start of a capture (in the "&" case) with the rest of the
// capture missing. Both are an error but a misplaced capture-default
// is more likely if we don't already have a capture default.
return Invalid(
[&] { Diag(Tok.getLocation(), diag::err_capture_default_first); });

The tentative parser now returns Incomplete for partially valid lambda introducers, preserving the lambda interpretation and allowing the proper diagnostic to be issued later.


Clang now correctly recognizes such constructs as malformed lambda introducers and emits the expected diagnostic — for example, “capture-default must be first” — consistent with direct initialization cases such as:

S s([a(42), &] {});

Full diff: https://github.com/llvm/llvm-project/pull/166180.diff

3 Files Affected:

  • (modified) clang/docs/ReleaseNotes.rst (+1)
  • (modified) clang/lib/Parse/ParseExprCXX.cpp (+25-17)
  • (modified) clang/test/Parser/lambda-misplaced-capture-default.cpp (+9)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 92fc9381a5868..63faf46895989 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -496,6 +496,7 @@ Bug Fixes to C++ Support
   nontrivial member when another member has an initializer. (#GH81774)
 - Fixed a template depth issue when parsing lambdas inside a type constraint. (#GH162092)
 - Diagnose unresolved overload sets in non-dependent compound requirements. (#GH51246) (#GH97753)
+- Fix incorrect diagnostics for lambdas with init-captures inside braced initializers. (#GH163498)
 
 Bug Fixes to AST Handling
 ^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/lib/Parse/ParseExprCXX.cpp b/clang/lib/Parse/ParseExprCXX.cpp
index 74f87a8cb63c3..03ae727168df0 100644
--- a/clang/lib/Parse/ParseExprCXX.cpp
+++ b/clang/lib/Parse/ParseExprCXX.cpp
@@ -772,9 +772,10 @@ bool Parser::ParseLambdaIntroducer(LambdaIntroducer &Intro,
 
   // Produce a diagnostic if we're not tentatively parsing; otherwise track
   // that our parse has failed.
-  auto Invalid = [&](llvm::function_ref<void()> Action) {
+  auto Result = [&](llvm::function_ref<void()> Action,
+                    LambdaIntroducerTentativeParse State) {
     if (Tentative) {
-      *Tentative = LambdaIntroducerTentativeParse::Invalid;
+      *Tentative = State;
       return false;
     }
     Action();
@@ -824,9 +825,11 @@ bool Parser::ParseLambdaIntroducer(LambdaIntroducer &Intro,
           break;
         }
 
-        return Invalid([&] {
-          Diag(Tok.getLocation(), diag::err_expected_comma_or_rsquare);
-        });
+        return Result(
+            [&] {
+              Diag(Tok.getLocation(), diag::err_expected_comma_or_rsquare);
+            },
+            LambdaIntroducerTentativeParse::Invalid);
       }
       ConsumeToken();
     }
@@ -861,9 +864,11 @@ bool Parser::ParseLambdaIntroducer(LambdaIntroducer &Intro,
         ConsumeToken();
         Kind = LCK_StarThis;
       } else {
-        return Invalid([&] {
-          Diag(Tok.getLocation(), diag::err_expected_star_this_capture);
-        });
+        return Result(
+            [&] {
+              Diag(Tok.getLocation(), diag::err_expected_star_this_capture);
+            },
+            LambdaIntroducerTentativeParse::Invalid);
       }
     } else if (Tok.is(tok::kw_this)) {
       Kind = LCK_This;
@@ -875,8 +880,9 @@ bool Parser::ParseLambdaIntroducer(LambdaIntroducer &Intro,
       // or the start of a capture (in the "&" case) with the rest of the
       // capture missing. Both are an error but a misplaced capture-default
       // is more likely if we don't already have a capture default.
-      return Invalid(
-          [&] { Diag(Tok.getLocation(), diag::err_capture_default_first); });
+      return Result(
+          [&] { Diag(Tok.getLocation(), diag::err_capture_default_first); },
+          LambdaIntroducerTentativeParse::Incomplete);
     } else {
       TryConsumeToken(tok::ellipsis, EllipsisLocs[0]);
 
@@ -899,14 +905,16 @@ bool Parser::ParseLambdaIntroducer(LambdaIntroducer &Intro,
         Id = Tok.getIdentifierInfo();
         Loc = ConsumeToken();
       } else if (Tok.is(tok::kw_this)) {
-        return Invalid([&] {
-          // FIXME: Suggest a fixit here.
-          Diag(Tok.getLocation(), diag::err_this_captured_by_reference);
-        });
+        return Result(
+            [&] {
+              // FIXME: Suggest a fixit here.
+              Diag(Tok.getLocation(), diag::err_this_captured_by_reference);
+            },
+            LambdaIntroducerTentativeParse::Invalid);
       } else {
-        return Invalid([&] {
-          Diag(Tok.getLocation(), diag::err_expected_capture);
-        });
+        return Result(
+            [&] { Diag(Tok.getLocation(), diag::err_expected_capture); },
+            LambdaIntroducerTentativeParse::Invalid);
       }
 
       TryConsumeToken(tok::ellipsis, EllipsisLocs[2]);
diff --git a/clang/test/Parser/lambda-misplaced-capture-default.cpp b/clang/test/Parser/lambda-misplaced-capture-default.cpp
index d65b875102da7..4f5bd6d7fa5e9 100644
--- a/clang/test/Parser/lambda-misplaced-capture-default.cpp
+++ b/clang/test/Parser/lambda-misplaced-capture-default.cpp
@@ -36,3 +36,12 @@ template <typename... Args> void Test(Args... args) {
   [... xs = &args, &] {};  // expected-error {{capture default must be first}}
 }
 } // namespace misplaced_capture_default_pack
+
+namespace GH163498 {
+struct S {
+  template <class T> S(T) {}
+};
+void t() {
+  S s{[a(42), &] {}}; // expected-error {{capture default must be first}}
+}
+}

Copy link
Collaborator

@AaronBallman AaronBallman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@a-tarasyuk a-tarasyuk merged commit bb6d2be into llvm:main Nov 6, 2025
11 checks passed
vinay-deshmukh pushed a commit to vinay-deshmukh/llvm-project that referenced this pull request Nov 8, 2025
…de braced initializers (llvm#166180)

Fixes llvm#163498

---

This PR addresses the issue of confusing diagnostics for lambdas with
init-captures appearing inside braced initializers.

Cases such as:

```cpp
S s{[a(42), &] {}};
```

were misparsed as C99 array designators, producing unrelated
diagnostics, such as `use of undeclared identifier 'a'`, and `expected
']'`

---


https://github.com/llvm/llvm-project/blob/bb9bd5f263226840194b28457ddf9861986db51f/clang/lib/Parse/ParseInit.cpp#L470


https://github.com/llvm/llvm-project/blob/bb9bd5f263226840194b28457ddf9861986db51f/clang/lib/Parse/ParseInit.cpp#L74


https://github.com/llvm/llvm-project/blob/bb9bd5f263226840194b28457ddf9861986db51f/clang/include/clang/Parse/Parser.h#L4652-L4655


https://github.com/llvm/llvm-project/blob/24c22b7de620669aed9da28de323309c44a58244/clang/lib/Parse/ParseExprCXX.cpp#L871-L879

The tentative parser now returns `Incomplete` for partially valid lambda
introducers, preserving the `lambda` interpretation and allowing the
proper diagnostic to be issued later.

---

Clang now correctly recognizes such constructs as malformed lambda
introducers and emits the expected diagnostic — for example,
“capture-default must be first” — consistent with direct initialization
cases such as:

```cpp
S s([a(42), &] {});
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Worse diagnostic for init-captured lambda appearing within a brace initializer

4 participants